package frame

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"runtime/debug"
	"strings"
	"sync"
	"time"

	"gitee.com/go-mao/mao/libs/try"
	"gitee.com/go-mao/mao/libs/utils"
	"github.com/gin-gonic/gin"
	_ "github.com/go-sql-driver/mysql"
	"xorm.io/core"
	"xorm.io/xorm"
)

type Server struct {
	once       sync.Once
	taskIndex  int64
	app        AppInterface
	web        *WebEngine
	ormEngine  *xorm.Engine
	hook       *hookDomain
	crontab    *crontabDomain //
	config     *configDomain
	logger     *logDomain
	permission *permissionDomain
}

func NewServer(app AppInterface) *Server {
	object := new(Server)
	object.app = app
	object.web = new(WebEngine)
	object.web.eg = gin.New()
	object.web.server = object
	object.hook = &hookDomain{server: object}
	object.permission = &permissionDomain{server: object}
	object.config = &configDomain{server: object}
	object.crontab = &crontabDomain{server: object}
	object.logger = &logDomain{server: object}
	object.taskIndex = time.Now().Unix()
	object.once = sync.Once{}
	return object
}

func (this *Server) TaskLine() *Taskline {
	return newTaskline(this, nil)
}

// 启动服务
func (this *Server) Run() {
	this.once.Do(func() {
		try.Do(this.run, func(e try.Exception) {
			log.Println("启动失败：", e.ErrCode(), e.ErrMsg())
		})
	})
}

// 启动app
func (this *Server) run() {
	//初始化目录
	this.initDir()
	//加载核心配置
	this.config.loadConfig(this.app.Config())
	//初始化日志
	this.logger.init()
	//
	if RunMode(this.config.RunMode) == RUN_MODE_PRODUCT {
		gin.SetMode(gin.ReleaseMode)
	}
	//初始化数据库
	this.initDatabase()
	//安装数据库
	this.installDatabases()
	//异常处理初始化
	this.web.Use(func(w *Webline) {
		try.Do(func() {
			w.Header("MAO-ID", fmt.Sprint(w.index))
			this.logger.Debugf("[WEB] [%d] %s %s", w.index, w.Request.Method, w.Request.RequestURI)
			w.Next()
		}, func(e try.Exception) {
			defer recover()
			msg := e.ErrMsg()
			if strings.Index(msg, "[SQL]") > 0 || e.ErrCode() == CODE_SQL {
				msg = "数据库错误"
			}
			if e.ErrCode() != code_nil {
				w.JSON(e.ErrCode(), nil, msg)
			}
			if e.ErrCode() < CODE_WARN {
				stack := debug.Stack()
				this.logger.Errorf("[WEB] [%d] %s %s %d %s \n%s", w.index, w.Request.Method, w.Request.RequestURI, e.ErrCode(), e.ErrMsg(), string(stack))
			}
			w.Abort()
		})
	})
	this.web.Use(this.web.middleMaxConnect())
	//应用初始化
	this.app.Init(this.web)
	//初始化模块
	this.initModules()
	//如果不启动web服务则直接返回，该配置主要用于测试时使用
	if _, ok := this.app.(NoWebInterface); ok {
		return
	}
	//异常处理初始化
	listen := this.config.Section("server").Key("listen").String()
	this.logger.Info("系统已启动，监听地址", listen)
	if err := this.web.Engine().Run(listen); err != nil {
		try.Throw(CODE_FATAL, "启动失败", err.Error())
	}
}

// 初始化目录
func (this *Server) initDir() {
	if err := os.MkdirAll(DIR_CONFIG, 0700); err != nil {
		try.Throw(CODE_FATAL, "无法创建config目录", err.Error())
		return
	}
	if err := os.MkdirAll(DIR_RUNTIME, 0700); err != nil {
		try.Throw(CODE_FATAL, "无法创建runtime目录", err.Error())
		return
	}
	if err := os.MkdirAll(DIR_LOGS, 0700); err != nil {
		try.Throw(CODE_FATAL, "无法创建logs目录", err.Error())
		return
	}
}

// 初始化数据库,并进行安装尝试
func (this *Server) initDatabase() {
	var dbType = "mysql"
	var username = this.config.Section("mysql").Key("username").String()
	var password = this.config.Section("mysql").Key("password").String()
	var host = this.config.Section("mysql").Key("host").String()
	var database = this.config.Section("mysql").Key("database").String()
	var dbSource = fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4", username, password, host, database)
	//初始化引擎
	engine, err := xorm.NewEngine(dbType, dbSource)
	if err != nil {
		try.Throw(CODE_SQL, "数据库初始失败", err.Error())
		return
	}
	engine.SetMaxOpenConns(500)
	engine.SetMapper(new(core.SameMapper))
	engine.StoreEngine("InnoDB")
	engine.Charset("utf8mb4")
	engine.SetTZLocation(time.Local)
	engine.EnableSessionID(true)
	engine.ShowSQL(true)
	this.logger.ShowSQL(true)
	engine.SetLogger(this.logger)
	if err := engine.Ping(); err != nil {
		try.Throw(CODE_SQL, "数据库连接失败", err.Error())
		return
	}
	this.ormEngine = engine
	this.logger.Info("数据库更新成功！")
}

// 获取所有模块
func (this *Server) GetModules() []ModuleInterface {
	return this.app.Modules()
}

// 获取所有组件
func (this *Server) GetComponents() []ComponentInterface {
	modules := this.app.Modules()
	comps := make([]ComponentInterface, 0)
	for _, item := range modules {
		comps = append(comps, item.Components()...)
	}
	return comps
}

// 安装数据库
func (this *Server) installDatabases() {
	lockFile := DIR_RUNTIME + "/install.lock"
	if utils.FileExists(lockFile) {
		return
	}
	//app安装执行
	if err := this.app.Install(); err != nil {
		try.Throw(CODE_SQL, "安装失败", err.Error())
		return
	}
	//模块数据库安装
	modules := this.app.Modules()
	for _, module := range modules {
		this.InstallModule(module)
	}
	//写入安装文件，避免重复安装
	data := time.Now().Format("2006-01-02 15:04:05")
	if err := ioutil.WriteFile(lockFile, []byte(data), 0600); err != nil {
		try.Throw(CODE_FATAL, "数据库安装锁定失败", err.Error())
	}
}

// 任务线包装
func (this *Server) wareTaskline(module ModuleInterface) ModuleInterface {
	if taskliner, ok := module.(SetSetTaskLiner); ok {
		taskliner.SetTaskline(this.TaskLine())
	}
	return module
}

// 初始化所有模块
func (this *Server) initModules() {
	modules := this.app.Modules()
	for _, module := range modules {
		this.LoadModule(module)
	}
}

// 加载模块路由，定时任务等
func (this *Server) LoadModule(module ModuleInterface) {
	this.wareTaskline(module)
	this.hook.init(module)
	this.crontab.init(module)
	this.config.init(module)
	this.web.Use(func(w *Webline) {
		if !module.Enable() {
			w.JSON(CODE_ERROR, nil, "该模块未加载！")
			w.Abort()
		}
	})
	module.Init(this.web)
}

// 安装模块的数据库
func (this *Server) InstallModule(module ModuleInterface) {
	//模块数据库安装
	this.wareTaskline(module)
	components := module.Components()
	for _, component := range components {
		models := make([]any, 0)
		for _, model := range component.Models() {
			models = append(models, model)
		}
		if err := this.ormEngine.Sync2(models...); err != nil {
			try.Throw(CODE_SQL, module.ModuleName()+"模块"+component.CompName()+"组件数据库安装失败", err.Error())
		}
	}
	module.Install()
}

// 删除模块（待开发）
func (this *Server) RemoveModule() {

}

// 默认异常记录
func (this *Server) defaultCatch(e try.Exception) {
	this.logger.Error(e.ErrMsg())
}
